package com.lonewalker.sparrow.handler;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.lonewalker.sparrow.annotation.ResponseResult;
import com.lonewalker.sparrow.annotation.IgnoreResponseResult;
import com.lonewalker.sparrow.domain.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;

/**
 * 返回结果处理器
 *
 * @author: lonewalker
 */
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
    /**
     * <p>请求类或方法是否包含了包装注解（{@link ResponseResult @ResponseResult}）标记，没有就直接返回，不需要重写返回体</p>
     * <p>加上了 {@link IgnoreResponseResult @IgnoreResponseResult} 注解的为白名单，即使有 {@link ResponseResult @ResponseResult} 注解也不执行统一返回。
     * 如果某个Controller下所有的方法都绕过，就把这个注解标记在控制器类上；如果只想忽略某个方法上就把它标记在该方法上即可。</p>
     *
     * @param returnType    返回类型
     * @param converterType 所选转换器类型
     * @return 如果需要调用 {@link #beforeBodyWrite} ，那么为 {@code true} ，否则 {@code false} 不重写返回体
     */
    @Override
    public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return !returnType.hasMethodAnnotation(IgnoreResponseResult.class);
    }

    /**
     * 重写返回体，在选择HttpMessageConverter之后、调用其写入方法之前调用。
     *
     * @param body                  需要重写的内容
     * @param returnType            {@code Controller} 方法返回的类型
     * @param selectedContentType   通过内容协商选择的内容类型
     * @param selectedConverterType 选择写入响应的转换器类型
     * @param request               当前 Http 请求
     * @param response              当前 Http 返回
     * @return 传入的原内容或重写的 {@link ApiResult}实例
     */
    @Override
    public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
                                  @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
        log.info("<统一返回组件> 返回内容=> {}", body);
        if (ObjectUtil.isNull(body)) {
            if (returnType.getParameterType().equals(String.class)) {
                response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                return JSONUtil.toJsonStr(ApiResult.success());
            }
            return ApiResult.successByData(body);
        }

        if (body instanceof ApiResult) {
            // 是 ApiResult 包装好的，说明是处理过异常的，直接返回
            return body;
        }

        if (body instanceof String) {
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            // 如果是字符串，这里需要转化为json，否则Spring会自动调用String消息转换器，导致类型转换错误。
            return JSONUtil.toJsonStr(ApiResult.successByData(body));
        }
        // 其他格式统一重写
        return ApiResult.successByData(body);
    }
}
